import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

// 定一个interface, 用于定义App的props类型
interface AppProps extends RouteComponentProps {
    // 表示字体颜色
    textColor: string;
}

// 定一个interface, 用于定义App的state类型
interface AppState {
    // 表示字体颜色
    content: string;
    // 显示loading
    showLoading: boolean;
}

// React.Component<P, S> 第一个泛型P表示props类型，第二个泛型S表示state类型
class App extends React.Component<AppProps, AppState> {
    // render() 函数应该为纯函数，这意味着在不修改组件 state 的情况下，
    // 每次调用时都返回相同的结果，并且它不会直接与浏览器交互。
    // 如需与浏览器进行交互，请在 componentDidMount() 或其他生命周期方法中执行你的操作。
    // 保持 render() 为纯函数，可以使组件更容易思考。
    // 关于纯函数定义
    // 1. 函数的返回结果只依赖于它的参数。
    // 2. 函数执行过程里面没有副作用。
    // 3. 没有额外的状态依赖。
    // 因为纯函数非常“靠谱”，执行一个纯函数你不用担心它会干什么坏事，它不会产生不可预料的行为，
    // 也不会对外部产生影响。不管何时何地，你给它什么它就会乖乖地吐出什么。
    // 如果你的应用程序大多数函数都是由纯函数组成，那么你的程序测试、调试起来会非常方便。
    render() {
        console.log('【App=>render】方法被执行');

        const show = this.state.showLoading ? <div>Loading...</div> : <div>{this.state.content}</div>;

        return (
            <div>
                <h1>First Page</h1>
                {show}
                <button
                    onClick={() => {
                        this.props.history.push('/second');
                    }}>
                    Second Page
                </button>
                <button
                    onClick={() => {
                        this.props.history.push('/hocPage');
                    }}>
                    Hoc Page
                </button>
                <button
                    onClick={() => {
                        this.props.history.push('/memoCallback');
                    }}>
                    useMemo和useCallback区别
                </button>
            </div>
        );
    }

    // 构造函数 => 如果不初始化 state 或不进行方法绑定，则不需要为 React 组件实现构造函数。
    // 在 React 组件挂载之前，会调用它的构造函数。
    // 要避免在构造函数中引入任何副作用或订阅。
    // 如遇到此场景，请将对应的操作放置在【componentDidMount】中。
    constructor(props: AppProps) {
        console.log('【App=>constructor】构造被调用');
        // React.Component 子类实现构造函数时，应在其他语句之前前调用 super(props)
        super(props);
        // 这里只需要对state进行赋值就行，不要调用setState()方法。
        // 如果不需要对state继续初始化, 可以不需要为 React 组件实现构造函数。
        this.state = {
            content: '',
            showLoading: true,
        };
    }

    // componentDidMount() 会在组件挂载后（插入 DOM 树中）立即调用。
    // 依赖于 DOM 节点的初始化应该放在这里。
    // 如需通过网络请求获取数据，此处是实例化请求的好地方。
    // 这个方法是比较适合添加订阅的地方。如果添加了订阅，请不要忘记在 componentWillUnmount() 里取消订阅。
    // 可以在 componentDidMount() 里直接调用 setState()
    // 它将触发额外渲染，但此渲染会发生在浏览器更新屏幕之前。
    // 如此保证了即使在 render() 两次调用的情况下，用户也不会看到中间状态。
    componentDidMount() {
        console.log('【App=>componentDidMount】生命周期被调用');
        // 通过网络请求获取数据
        this.getData();
    }

    // 【getSnapshotBeforeUpdate】的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递
    // getSnapshotBeforeUpdate() 在最近一次渲染输出（提交到 DOM 节点）之前调用。
    // 它使得组件能在发生更改之前从 DOM 中捕获一些信息（例如，滚动位置）。
    // 此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
    getSnapshotBeforeUpdate(prevProps: Readonly<AppProps>, prevState: Readonly<AppState>): any | null {
        // 返回一个黑色
        return '#000';
    }

    // componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
    // 如果 shouldComponentUpdate() 返回值为 false，则不会调用 componentDidUpdate()。
    componentDidUpdate(prevProps: Readonly<AppProps>, prevState: Readonly<AppState>, snapshot?: any) {
        console.log('【App=>componentDidUpdate】生命周期被调用');
        if (this.props.textColor !== snapshot) {
            console.log(`【App=>snapshot】的值为: ${snapshot}`);
        }
    }

    // componentWillUnmount() 会在组件卸载及销毁之前直接调用。
    // 在此方法中执行必要的清理操作，
    // 例如，清除 timer，取消网络请求或清除在 componentDidMount() 中创建的订阅等。
    // componentWillUnmount() 中不应调用 setState()，因为该组件将永远不会重新渲染。
    componentWillUnmount() {
        console.log('【App=>componentWillUnmount】生命周期被调用');
    }

    // 此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数，并返回一个值以更新 state
    static getDerivedStateFromError(error: any) {}

    // 获取数据
    private getData() {
        const url = 'http://blogapi.hellosu.top/v1/blog/recommendedArticle';
        fetch(url, {
            headers: { 'content-type': 'application/json' },
            method: 'GET',
        })
            .then(rsp => {
                rsp.json().then(data => {
                    this.setState({ content: JSON.stringify(data) });
                });
            })
            .catch(() => this.setState({ content: 'Error' }))
            .finally(() => {
                this.setState({ showLoading: false });
            });
    }
}

export default App;
